<?php
$host = '0.0.0.0';
$port = 8888;
$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
socket_bind( $listen_socket, $host, $port );
if (true !== socket_set_option($listen_socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
    errhandle(__LINE__);
}
//这项拿掉会出现地址占用的报错
if (true !== socket_set_option($listen_socket, SOL_SOCKET, SO_REUSEPORT, 1)){
    errhandle(__LINE__);
}

socket_listen( $listen_socket );
// END 创建服务器完毕
echo "listen:".print_r($listen_socket,1).PHP_EOL;

$client = [ $listen_socket ];
$write = [];
$exp = [];

// 开始进入循环
while( true ){
    $read = $client;
    echo "watchs:".ids($read).PHP_EOL;
    // 当select监听到了fd变化，注意第四个参数为null
    // 如果写成大于0的整数那么表示将在规定时间内超时
    // 如果写成等于0的整数那么表示不断调用select，执行后立马返回，然后继续
    // 如果写成null，那么表示select会阻塞一直到监听发生变化
    if( socket_select( $read, $write, $exp, null ) > 0 ){
        echo "changes:".ids($read).PHP_EOL;
        // 判断listen_socket有没有发生变化，如果有就是有客户端发生连接操作了
        if( in_array( $listen_socket, $read ) ){
            // 将客户端socket加入到client数组中
            $client_socket = socket_accept( $listen_socket );
            echo "new connect client:".intval($client_socket).PHP_EOL;
            $client[] = $client_socket;
            // 然后将listen_socket从read中去除掉
            $key = array_search( $listen_socket, $read );
            unset( $read[ $key ] );
        }

        // 查看去除listen_socket中是否还有client_socket
        if( count( $read ) > 0 ){
            foreach( $read as $socket_item ){
                // 从可读取的fd中读取出来数据内容，然后发送给其他客户端
                $content = socket_read( $socket_item, 5 );
                echo "Recv:".$content;
                $msg="From ".intval($socket_item).":".$content.PHP_EOL;
                // 循环client数组，将内容发送给其余所有客户端
                foreach( $client as $client_socket ){
                    // 因为client数组中包含了 listen_socket 以及当前发送者自己socket，所以需要排除二者
                    //必须排除 listen_socket 否者出现 Broken pipe
                    if( $client_socket != $listen_socket /*&& $client_socket != $socket_item*/ ){
                        socket_write( $client_socket, $msg, strlen( $msg ) );
                    }
                }
            }
        }
    }
    // 当select没有监听到可操作fd的时候，直接continue进入下一次循环
    else {
        echo "no select \n";
        continue;
    }
    echo PHP_EOL.str_repeat('-',30).PHP_EOL;

}

function ids($arr){
    $arr= array_map(function($v){
        return intval($v);
    },$arr);
    return implode(",",$arr);
}
function errhandle($line_num,$exit=true)
{
    echo $line_num.":".socket_last_error() . ":" . socket_strerror(socket_last_error()) . PHP_EOL;
    if($exit){
        exit();
    }

}